package com.me.study.designPatterns.Singleton;

import com.me.common.util.ConcurrentUtil;
import com.me.common.util.SleepUtil;
import com.me.study.javaCore.classLoader.customClassLoader.MyClassLoader;

import java.lang.reflect.Method;
import java.util.concurrent.CyclicBarrier;

/**
 * 懒汉单例: 双重检查锁（DCL）
 *
 * @author ME
 * @date 2019/2/28 19:25
 */
public class DCLSingleton {

    public static void main(String[] args) throws Exception {
        while (true) {
            int count = 1000;
            CyclicBarrier barrier = new CyclicBarrier(count);
            Class<?> singleClass = new MyClassLoader().loadClass("com.me.study.designPatterns.Singleton.DCLSingleton");
            Method instance = singleClass.getDeclaredMethod("getInstance");
            Object obj = instance.invoke(null);
            Method print = singleClass.getDeclaredMethod("print");
            print.setAccessible(true);
            for (int i = 0; i < count; i++) {
                new Thread(() -> {
                    ConcurrentUtil.barrierAwait(barrier);

                    try {
                        print.invoke(obj);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
            SleepUtil.ms(1);
        }
    }

    /**
     * volatile 保证可见性和有序性，此处需要的是有序性：<p>
     * 如果没有 volatile 有可能 instance 先
     */
    private volatile static DCLSingleton instance;

    private DCLSingleton() {
        System.out.println("实例化了。。。");
        this.i = 99;
    }

    public static DCLSingleton getInstance() {
        // TODO 性能提升：第一次非空是为了降低锁的颗粒度，排除获取对象的绝大部分线程进入锁代码块，是从性能提升上考虑的
        if (instance == null) {// (1)
            synchronized (DCLSingleton.class) {
                /**
                 * 上锁之后重新判断一次是否为空，why？
                 * 因为多线程环境下可能存在，2个线程同时进入了 代码(1)，然后由于 synchronized，排队进入同步块
                 * 第二个进入同步块的线程如果不判断 `Singleton == null`，则会初始化两次，就不是单例了！！！
                 */
                // TODO 线程安全：第二次非空才是为了线程安全，避免多个排队进入代码库的线程重复初始化
                if (instance == null) {// (2)
                    /*
                     * 这条语句实际上包含了三个操作：
                     * 1.分配对象的内存空间；
                     * 2.初始化对象；
                     * 3.设置instance指向刚分配的内存地址
                     *
                     * TODO （未重现）如果没有 volatile，则可能会先分配内存地址，还没初始化对象，则第二个线程会 NullPointerException
                     */
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }

    private Integer i;

    /**
     * 随便整个方法验证 不加 volatile 时的空指针
     */
    private void print() {
        if (i != 99) {
            System.out.println(i);
        }
    }

}
